package ns.gflex.orm

import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;


import org.apache.log4j.Logger;
import org.codehaus.groovy.grails.commons.ApplicationHolder;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Projections;

import flex.messaging.FlexContext;
import flex.messaging.FlexSession;
import grails.orm.HibernateCriteriaBuilder;
import grails.util.Environment;
import groovy.sql.Sql;

import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
/**
 * orm工具类
 * 创建时间 2010-6-1
 * @author wangchu
 */
abstract class GForm{
	static Logger log = Logger.getLogger(GForm.class);
	static Map map = [:]

	static Session getHibernateSession(Object domain){
		def session
		forDomain(domain).withSession{session=it}
		return session
	}
	/**
	 * 得到groovy sql 实例
	 * @return
	 */
	static Sql getGroovySql(Object domain){
		new Sql(getHibernateSession(domain).connection())
	}

	static MessageSource getMessageSource(){
		if(!map.messageSource){
			ApplicationContext ctx = (ApplicationContext)ApplicationHolder.getApplication().getMainContext();
			map.messageSource = ctx.getBean("messageSource");
		}
		return map.messageSource
	}

	static def save(Map map,boolean isMerge = false,Object domain){
		Class cls = forDomain(domain)
		def newEntity = cls.newInstance(map)
		if(map.id){
			//原来的代码newEntity是离线的，会触发beforeInsert，导致dateCreated更新，其实是update
			def oldEntity = cls.get(map.id)
			BeanUtils.copyProperties(newEntity,oldEntity,['id', 'version', 'metaClass'].toArray(new String[0]))
			saving(oldEntity,isMerge)
		}
		else
			saving(newEntity,isMerge)
	}

	/**
	 * 通用查询
	 * @param params 参数，例如： [type:[eq:[['name','cat']]],
	 * 			            or:[eq:[['name','c2'],['name','c1']]],
	 * 		           		maxResults:[10],
	 * 			            order:[['name','desc']]
	 * 			        	]
	 * @param domain orm实体类--字符串或者Class
	 * @return 结果列表	  
	 */
	static List list(Map param,Object domain) {
		domain=forDomain(domain)
		log.info "list - ${domain} param:$param"

		if(param?.projections){
			log.info "resultTransformer to CriteriaSpecification.ALIAS_TO_ENTITY_MAP"
			param.put 'resultTransformer', [
				CriteriaSpecification.ALIAS_TO_ENTITY_MAP
			]
		}

		criteriaQuery(domain,param,false)
	}


	/**
	 * 通用计数函数
	 * @param param 参数map
	 * @param domain 实体--字符串或者Class
	 * @return
	 */
	static int count(Map param,Object domain) {
		domain=forDomain(domain)
		log.info "count - ${domain} param:$param"
		criteriaQuery(domain,param,true)
	}

	/**
	 * 保存到数据库
	 * @param entity
	 * @return
	 */
	static def saving(def entity,boolean isMerge = false){
		log.info "saving  ${entity.class}"

		//错误信息可能在中间过程中产生，末状态正确时不一定为空，所以必须先清理
		entity.clearErrors()
		if(entity.validate()){
			/* 如果存在unique限制，当save为update时导致NonUniqueObjectException异常
			 * 原因是查询是否唯一时，导致session有两个id相同对象，grails目前处理不够好。
			 * 如果用entity.merge()，部分功能，如autoTimeStrape无法自动完成
			 * 暂时通过清空session处理
			 * */
			if(!isMerge){
				log.debug "save ${entity.class} ${entity}"
				entity.save()
			}
			else{
				log.debug "merge ${entity.class} ${entity}"
				entity.merge()
			}
		}
		else{
			StringBuilder sb = new StringBuilder()
			entity.errors.allErrors.each{
				log.error(it)
				//messageBundle配置参考log.error(it)打印的error.code，
				//如com.beeb.teller.TeTransInfo.transCode.unique.error
				sb.append("${messageSource.getMessage(it, Locale.default)}\n")
			}
			throw new RuntimeException(sb.toString())
		}
	}

	static void deleteByStringList(List valueList,String field,Object domain) {
		if(valueList){
			deleteByInHql("""'${valueList.join("','")}'""",field,domain)
		}
	}

	static void deleteByNumberList(List valueList,String field,Object domain) {
		if(valueList){
			deleteByInHql(valueList.join(','),field,domain)
		}
	}

	static void deleteByInHql(String valueString,String field,Object domain) {
		if(valueString){
			domain=forDomain(domain)
			log.info  "delete $domain.simpleName $field in $valueString"
			domain.executeUpdate("""delete $domain.simpleName where $field in ($valueString)""")
		}
	}

	static public Class forDomain(Object domain){
		if(domain instanceof Class)
			return domain
		else
			return Thread.currentThread().contextClassLoader.loadClass(domain.toString())
	}

	/**
	 * 使用hibernate criteria 查询
	 * @param domainClass
	 * @param param
	 * @param isCount 是否计数
	 * @return
	 */
	static public Object criteriaQuery(Class domainClass,Map param,boolean isCount){
		def criteria = domainClass.createCriteria()
		if(isCount)
			criteria.count(makeCriteria(param))
		else
			criteria.list(makeCriteria(param))
	}


	/**
	 * 通过Map构建HibernateCriteriaBuilder query closure
	 * @param param，例如： [type:[eq:[['name','cat']]],
	 * 			            or:[eq:[['name','c2'],['name','c1']]],
	 * 		           		maxResults:[10],
	 * 			            order:[['name','desc']]
	 * 			        	]
	 * 
	 * @return 查询闭包
	 * @see HibernateCriteriaBuilder
	 */
	static public def makeCriteria(Map param){
		return {
			param.each { k,v ->
				log.debug "invokeMethod $k($v)"
				//如果v是Map，递归makeQuery
				if(v instanceof Map)
					invokeMethod k,makeCriteria(v)
				//否则v必须为list，将list的每个值作为参数数组，循环调用Builder的函数
				else
					v.each {
						def args = (it instanceof List)?it.toArray():it

						//通过id查询不能自动转换Integer为Long
						if(k=='eq'&&(args[0]=~'.*[iI]d$')&&args[1] instanceof Integer)
							args[1]= args[1].longValue()
						//HibernateCriteriaBuilder不支持sqlGroupProjection
						if(k=='sqlGroupProjection')
							addProjectionToList(Projections.invokeMethod('sqlGroupProjection',args),args[2][0])
						else if(k=='sqlProjection')
							addProjectionToList(Projections.invokeMethod('sqlProjection',args),args[1][0])
						else
							invokeMethod k,args
					}
			}
		}
	}
}
